#import "lib/lib.typ" as lib

#show: lib.lib-style

#lib.make-title(id: 22122769, name: "屠淳旭", title: [《编译原理》实验报告一])

= 实验目的

- 根据 PL/0 语言的文法规范，编写 PL/0 语言的标识符识别程序。
- 通过设计调试标识符识别程序，实现从源程序中分出各个标识符的方法；加深对课堂教学的理解；为后序词法分析程序的实现打下基础。
- 掌握从源程序文件中读取有效字符的方法和产生源程序的内部表示文件的法。
- 掌握识别标识符的实现方法。
- 上机调试完成的识别标识符程序的实现。

= 实验内容

输入 PL/0 语言源程序，输出源程序中所有标识符的出现次数。

= 实验要求

- 识别程序读入 PL/0 语言源程序（文本文件），识别结果也以文本文件保存。
- 按标识符出现的顺序输出结果，每个标识符一行，采用二元式序列，即：(标识符值, 标识符出现次数)
- 源程序中字符不区分大小写，即：“a1”和“A1”是同一个标识符。
- 准备至少 5 组测试用例，每组测试用例包括：输入源程序文件和输出结果。
- 测试用例应该考虑各种合法标识符的组合情况。

= 设计思路

  首先读入文件内容，对文本进行分词。分词用链表组织数据结构，由于不区分大小写，统一小写表示。链表中每个数据块包含分词和它的符号类型信息。随后遍历链表，判断每个分词的符号类型，类型包括标识符等。最后对链表中的标识符，以标识符作为key，使用散列统计每个标识符出现的次数，得到结果后输出。

= 代码

  这里给出主要代码和实现流程，一些辅助性的代码请参考附件。本实验使用Makefile进行编译。

#lib.code(caption: "编译和使用说明")[
```bash
make clean
make
./bin/main path/to/input
```]

#lib.code(caption: "分词函数")[
```C
struct TokenBlock *get_tokens(FILE *fp) {
  CharType current_type;
  char buf[MAX_TOKEN_LEN], buf_char;
  int current_char_idx = 1;

  struct TokenBlock head, *tail_ptr = &head;
  head.nextBlock = NULL;

  while ((buf_char = fgetc(fp)) != EOF) {
    if ((current_type = get_char_type(buf_char)) == BLANK) {
      continue;
    }

    buf[0] = buf_char;

    while ((buf_char = fgetc(fp)) != EOF &&
           get_char_type(buf_char) == current_type) {
      buf[current_char_idx++] = buf_char;
    }
    buf[current_char_idx] = 0;

    if (strcmp(buf, ");") == 0) {
      tail_ptr = append_token(tail_ptr, ")");
      tail_ptr = append_token(tail_ptr, ";");
    } else {
      tail_ptr = append_token(tail_ptr, buf);
    }

    if (buf_char == EOF) {
      break;
    } else if (get_char_type(buf_char) != BLANK) {
      fseek(fp, -1, SEEK_CUR);
    }
    current_char_idx = 1;
  }

  return head.nextBlock;
}
```
]

#lib.code(caption: "符号类型判别函数")[
```C
// 识别symbol的类型
SymbolType determine_symbol_type(const char *token) {
  // 临时缓冲区用于大小写不敏感比较
  char lower_token[MAX_TOKEN_LEN];
  strncpy(lower_token, token, MAX_TOKEN_LEN);
  lower_token[MAX_TOKEN_LEN - 1] = '\0';

  // 转换为小写（保留字不敏感）
  for (int i = 0; lower_token[i]; i++) {
    lower_token[i] = tolower(lower_token[i]);
  }

  // 匹配保留字
  for (size_t i = 0; i < sizeof(reserved_map) / sizeof(reserved_map[0]); i++) {
    if (strcmp(lower_token, reserved_map[i].keyword) == 0) {
      return reserved_map[i].sym;
    }
  }
  // 数字类型
  if (is_all_digits(token)) {
    return NUMBER_SYM;
  }
  // 标识符
  if (is_valid_ident(token)) {
    return IDENT_SYM;
  }
  // 运算符和界限符
  return determine_operator_type(token);
}
```
]

#lib.code(caption: "判别每个token的符号类型")[
```C
// 遍历链表，确定每个token的符号类型
void type_token_list(struct TokenBlock *head) {
  struct TokenBlock *current = head;
  while (current != NULL) {
    current->sym = determine_symbol_type(current->token);
    current = current->nextBlock;
  }
}
```]

#lib.code(caption: "对外功能接口")[
```C
struct TokenBlock *get_typed_tokens(FILE *fp) {
  struct TokenBlock *res = get_tokens(fp);
  type_token_list(res);
  return res;
}
```]

#lib.code(caption: "对外功能接口")[
```C
struct TokenBlock *get_typed_tokens(FILE *fp) {
  struct TokenBlock *res = get_tokens(fp);
  type_token_list(res);
  return res;
}
```]

#lib.code(caption: "主函数")[
```C
#include "TokenBlock.h"
#include <stdexcept>
#include <unordered_map>

int main(int argc, char *argv[]) {
  std::unordered_map<std::string, int> wordCount;

  FILE *fp = fopen(argv[1], "r");
  struct TokenBlock *tokens = get_typed_tokens(fp);

  for (struct TokenBlock *p = tokens; p; p = p->nextBlock) {
    if (p->sym != IDENT_SYM) {
      continue;
    }
    try {
      int count = wordCount.at(p->token);
      wordCount[p->token] = count + 1;
    } catch (const std::out_of_range &e) {
      wordCount[p->token] = 1;
    }
  }

  for (const auto &pair : wordCount) {
    printf("(%s: %d)\n", pair.first.c_str(), pair.second);
  }

  return 0;
}
```]

= 测试/运行结果

  测试结果如@img:test 所示。

#lib.graph(path: "../assets/cp_lab1_test.png", caption: "测试样例的测试结果") <img:test>

= 实验体会

  通过本次实验，我深入理解了词法分析中标识符识别的核心逻辑，掌握了对字符流的分段处理、符号类型判别和哈希统计等技术要点，特别是在处理保留字与标识符的优先级冲突时，通过预转换小写和优先匹配机制解决了误判问题。实践中认识到边界条件测试的重要性，如文件结束符处理和特殊符号组合分割，这些经验为后续语法分析器的开发奠定了扎实的工程基础。
